
/*
 * (C)opyright 2010, Alessandro Agosto. All rights reserved.
 * This file is part of the "pycoev" project.
 * To get more informations about the license read the LICENSE file.
 *
 * LoopType method definitions.
 */


#include "pyc_pyapis.h"
#include "pyc_event.h"
#include "pyc_loop.h"

#ifndef DISABLE_DEBUG
#   define DEBUG(...) fprintf(stderr, __VA_ARGS__)
#else
#   define DEBUG(...)
#endif


/*
 * Internal function, executes the callback function.
 * NOTE: since the picoev callback must accept predefinied arguments
 * I pass the Loop * (python object) as last argument (void * loop_self)
 * casting it to Loop * again as Loop *self.
 */
void
PycLoop_CallbackHandler(picoev_loop *loop, int fd, int events, void* loop_self)
{
    PyObject *res, *pyevents;
    Loop *self = (Loop *)loop_self;

    pyevents = PyInt_FromLong(events);

    /*cb_arg represents the callback argument*/
    if (NULL == self->cb_arg) {
        res = PyObject_CallFunctionObjArgs(self->callback,
                                           (PyObject *)self,
                                           pyevents, NULL);
    }
    else {
        res = PyObject_CallFunctionObjArgs(self->callback,
                                           (PyObject *)self,
                                           pyevents,
                                           self->cb_arg, NULL);
    }

    if (!res) {
        PyErr_WriteUnraisable(self->callback);
    }
    else {
        Py_DECREF(res);
    }

    Py_DECREF(pyevents);

    return;
}


/*
 * "add" method
 * @params: fd, events, timeout_in_secs, callback, cb_arg (by keyword)
 * cb_arg is optional
 * @return: Py_True on success, Py_False on failure, an exception on error.
 */
PyObject *
PycLoop_Add(Loop *self, PyObject *args, PyObject *kwargs)
{
    PyObject *callback, *cb_arg, *fd;
    int fdid, events, timeout, res;

    static char *kwlist[] = { "fd", "events", "timeout", "callback", "cb_arg",
                              NULL };
    res = PyArg_ParseTupleAndKeywords(args, kwargs, "OiiO|O:add", kwlist, &fd,
                                      &events, &timeout, &callback, &cb_arg);
    if (!res) {
        Pyc_Fatal(NULL, NULL);
    }
    
    fdid = PyObject_AsFileDescriptor(fd);
    if (PYC_ERROR == fdid) {
        Pyc_Fatal(NULL, NULL);
    }
    
    DEBUG("adding fd #%d\n", fdid);

    /*do not decref the fd or it won't work*/

    res = PyCallable_Check(callback);
    if (!res) {
        Pyc_Fatal(Pyc_Error, "'callback' must be a callable object.");
    }
    else {
        self->callback = callback;
    }

    Py_DECREF(callback);

    if (cb_arg) {
        Py_INCREF(cb_arg);
        self->cb_arg = cb_arg;
    }

    //Py_XDECREF(cb_arg); /*callback might be omitted*/

    if (events != PICOEV_READ && events != PICOEV_WRITE &&
        events != PICOEV_READWRITE) {
            Pyc_Fatal(Pyc_Error, "'events' must be READ, WRITE or READWRITE.");
    }

    /*register as function a wrapper to execute the python function*/
    res = picoev_add(self->loop, fdid, events, timeout,
                     PycLoop_CallbackHandler, (void *)self);
    if (PYC_ERROR == res) {
        Py_RETURN_FALSE; /*couldn't register the fd*/
    }

    Py_RETURN_TRUE;
}


/*
 * "delete" method
 * @params: fd (positional)
 * @return: Py_True on success, Py_False on failure
 */
PyObject *
PycLoop_Delete(Loop *self, PyObject *args)
{
    PyObject *fd;
    int fdid, res;

    res = PyArg_ParseTuple(args, "O:del", &fd);
    if (!res) {
        Pyc_Fatal(NULL, NULL);
    }

    fdid = PyObject_AsFileDescriptor(fd);
    if (PYC_ERROR == fdid) {
        Pyc_Fatal(NULL, NULL);
    }
    
    DEBUG("deleting fd #%d\n", fdid);

    /*Py_DECREF(fd);*/

    res = picoev_del(self->loop, fdid);
    if (PYC_ERROR == res) {
        Py_RETURN_FALSE;
    }
    /*else*/
    Py_RETURN_TRUE;
}


/*
 * "loop_once" method
 * @params: max_wait (positional)
 * @return: True on success, False otherwise
 */
PyObject *
PycLoop_LoopOnce(Loop *self, PyObject *args)
{
    int max_wait, res;

    res = PyArg_ParseTuple(args, "i:loop_once", &max_wait);
    if (!res) {
        Pyc_Fatal(NULL, NULL);
    }

    res = picoev_loop_once(self->loop, max_wait);
    if (PYC_ERROR == res) {
        Py_RETURN_FALSE;
    }

    if (PyErr_Occurred()) {
        Pyc_Fatal(NULL, NULL);
    }

    Py_RETURN_TRUE;
}

/*
 * "set_timeout" method
 * Updates the timeout for a given file descriptor.
 * @params: the file descriptor and an integer representing the new timeout
 * @return: Py_None
 */
PyObject *
PycLoop_SetTimeout(Loop *self, PyObject *args)
{
    int new_timeout, fdid, res;
    PyObject *fd;

    /*do not DECREF the fd*/
    res = PyArg_ParseTuple(args, "Oi:set_timeout", &fd, &new_timeout);
    if (!res) {
        Pyc_Fatal(NULL, NULL);
    }

    fdid = PyObject_AsFileDescriptor(fd);
    if (PYC_ERROR == fdid) {
        Pyc_Fatal(NULL, NULL);
    }

    picoev_set_timeout(self->loop, fdid, new_timeout);
    
    Py_RETURN_NONE;
}


/*
 * "loop" method
 * A shortcut to avoid the loop_once in a while 1
 * Loops internally
 * @params: as "loop_once"
 * @return: Py_None
 * NOTE: this require an internal SIGINT catch or it won't stop
 */
PyObject *
PycLoop_Loop(Loop *self, PyObject *args)
{
    int max_wait, res;

    res = PyArg_ParseTuple(args, "i:loop_once", &max_wait);
    if (!res) {
        Pyc_Fatal(NULL, NULL);
    }

    while (1) {
        DEBUG(".");
        picoev_loop_once(self->loop, max_wait);
    };

    Py_RETURN_NONE;
}